package com.fsgom.common.db;


import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;


import com.fsgom.common.util.ResourceUtils;
import com.fsgom.common.util.ServerInfoUtils;
import com.fsgom.common.xml.XmlHandler;
import com.fsgom.common.db.DataSourceConfig;


public class DataSourceConfiguration implements InitializingBean {
	private boolean useProxy = true;
	private Resource group;
	private final Map<String, SqlMapConfig> sqlmaps = new HashMap<String, SqlMapConfig>();
	private final Map<String, DataSourceConfig> datasources = new HashMap<String, DataSourceConfig>();
	
	@Deprecated
	public void setXa(boolean xa) {
	}

	/**
	 * {@link ProxyDataSource} 사용 여부를 설정한다
	 * 
	 * @param useProxy {@link ProxyDataSource} 사용 여부
	 */
	public void setUseProxy(boolean useProxy) {
		this.useProxy = useProxy;
	}

	/**
	 * Group을 설정한다
	 * 
	 * @param group
	 */
	public void setGroup(Resource group) {
		this.group = group;
	}

	/**
	 * Group 정보를 처리한다
	 * 
	 * @throws IOException
	 */
	private void processGroup() throws IOException {
		XmlHandler xmlHandler = new XmlHandler(group.getInputStream());
		List<XmlHandler> groupHandlers = xmlHandler.selectAll("group");

		if (CollectionUtils.isEmpty(groupHandlers)) {
			return;
		}

		for (XmlHandler groupHandler : groupHandlers) {
			String groupId = groupHandler.get("@id");
			List<XmlHandler> datasourceHandlers = groupHandler.selectAll("datasource");

			for (XmlHandler datasourceHandler : datasourceHandlers) {
				String datasourceId = datasourceHandler.get("@id");
				Properties groupProperties = getGroupProperties(datasourceHandler);

				DataSourceConfig dataSourceConfig = datasources.get(datasourceId);

				if (dataSourceConfig == null) {
					continue;
				}

				dataSourceConfig.addGroupProperties(groupId, groupProperties);
			}
		}
	}

	/**
	 * Group 프로퍼티 정보를 처리한다
	 * 
	 * @param datasourceHandler {@link XmlHandler}
	 * @return
	 */
	private Properties getGroupProperties(XmlHandler datasourceHandler) {
		Properties props = new Properties();
		List<XmlHandler> subHandlers = datasourceHandler.selectAll("*");

		for (XmlHandler subHandler : subHandlers) {
			props.put(subHandler.getRootName(), subHandler.get("."));
		}

		return props;
	}

	/**
	 * SqlMap을 설정한다
	 * 
	 * @param sqlmap sqlmap 설정 파일
	 * @throws IOException 파일이 없는 경우
	 */
	public void setSqlmap(Resource sqlmap) throws IOException {
		if (!ResourceUtils.isValid(sqlmap)) {
			return;
		}

		XmlHandler xmlHandler = new XmlHandler(sqlmap.getInputStream());
		List<XmlHandler> subHandlers = xmlHandler.selectAll("sqlmap");

		if (CollectionUtils.isEmpty(subHandlers)) {
			return;
		}

		for (XmlHandler subHandler : subHandlers) {
			String id = subHandler.get("@id");
			SqlMapConfig sqlMapConfig = new SqlMapConfig();
			sqlMapConfig.setSqlMapId(id);
			sqlMapConfig.setDataSourceId(subHandler.get("@datasource-id"));
			sqlMapConfig.setBasePackage(subHandler.get("base/@package"));
			sqlMapConfig.setResources(subHandler.getValues("resource/@path"));
			sqlmaps.put(id, sqlMapConfig);
		}
	}

	/**
	 * 데이터소스 프로퍼티를 가져온다
	 * 
	 * @param xmlHandler {@link XmlHandler}
	 * @return 데이터소스 프로퍼티
	 */
	private String getProperties(XmlHandler xmlHandler) {
		if (xmlHandler.containsKey("@properties")) {
			return xmlHandler.get("@properties");
		}

		List<XmlHandler> propertiesHandler = xmlHandler.selectAll("properties/property");

		if (CollectionUtils.isEmpty(propertiesHandler)) {
			return null;
		}

		List<String> properties = new ArrayList<String>();

		for (XmlHandler propertyHandler : propertiesHandler) {
			properties.add(propertyHandler.get("."));
		}

		int hashCode = Math.abs(ServerInfoUtils.getHostName().hashCode()) % properties.size();
		return properties.get(hashCode);
	}

	/**
	 * 데이터소스를 설정한다
	 * 
	 * @param datasource 데이터소스 설정 파일
	 * @throws IOException 파일이 없는 경우
	 */
	public void setDatasource(Resource datasource) throws IOException {
		if (!ResourceUtils.isValid(datasource)) {
			return;
		}

		XmlHandler xmlHandler = new XmlHandler(datasource.getInputStream());
		List<XmlHandler> subHandlers = xmlHandler.selectAll("datasource");

		if (CollectionUtils.isEmpty(subHandlers)) {
			return;
		}

		for (XmlHandler subHandler : subHandlers) {
			String id = subHandler.get("@id");
			DataSourceConfig dataSourceConfig = new DataSourceConfig();
			dataSourceConfig.setDataSourceId(id);
			dataSourceConfig.setType(subHandler.get("@type"));
			boolean readonly = subHandler.containsKey("@readonly") ? Boolean.valueOf(subHandler.get("@readonly")) : false;
			dataSourceConfig.setReadonly(readonly);

			String properties = getProperties(subHandler);

			if (StringUtils.isEmpty(properties)) {
				throw new IllegalArgumentException("properties 값이 올바르지 않습니다");
			}

			dataSourceConfig.setProperties(properties);
			datasources.put(id, dataSourceConfig);
		}
	}

	/**
	 * {@link ProxyDataSource} 사용 여부를 리턴한다
	 * 
	 * @return {@link ProxyDataSource} 사용 여부
	 */
	public boolean useProxy() {
		return useProxy;
	}

	/**
	 * SqlMap 정보를 리턴한다
	 * 
	 * @return sqlmap 정보
	 */
	public final Map<String, SqlMapConfig> getSqlMaps() {
		return sqlmaps;
	}

	/**
	 * 데이터소스 정보를 리턴한다
	 * 
	 * @return 데이터소스 정보
	 */
	public final Map<String, DataSourceConfig> getDataSources() {
		return datasources;
	}

	/**
	 * SqlMap ID를 이용하여 데이터소스 ID를 가져온다
	 * 
	 * @param sqlMapId sqlmap id
	 * @return 데이터소스 ID
	 */
	public String getDataSourceId(String sqlMapId) {
		SqlMapConfig sqlMapConfig = sqlmaps.get(sqlMapId);

		if (sqlMapConfig == null) {
			return null;
		}

		return sqlMapConfig.getDataSourceId();
	}

	/**
	 * 데이터소스 ID를 이용하여 SqlMap ID를 가져온다
	 * 
	 * @param dataSourceId 데이터소스 ID
	 * @return sqlmap id
	 */
	public String getSqlMapId(String dataSourceId) {
		for (Entry<String, SqlMapConfig> entry : sqlmaps.entrySet()) {
			if (dataSourceId.equals(entry.getValue().getDataSourceId())) {
				return entry.getKey();
			}
		}

		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		if (!ResourceUtils.isValid(group)) {
			return;
		}

		processGroup();
	}
}
